/*
 * Copyright (c) 2012 Sean Connelly
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//
// signed integer limited only by memory (basically, taking a ubig and adding a sign)
//

var ubig = require('./ubig.js');

function big_create(num_sgn, num_ubig)
{
	return {
			_getUbig: function() {
					return num_ubig;
				},
			copy: function(r) {
					num_ubig.copy(r._getUbig());
					num_sgn = r.isNeg();
					return this;
				},
			isNeg: function() {
					return !this.isZero() && num_sgn;
				},
			isPos: function() {
					return !this.isZero() && !num_sgn;
				},
			isZero: function() {
					return num_ubig.isZero();
				},
			isOne: function() {
					return num_ubig.isOne() && !num_sgn;
				},
			isNegOne: function() {
					return num_ubig.isOne() && num_sgn;
				},
			isOdd: function() {
					return num_ubig.isOdd();
				},
			isEven: function() {
					return num_ubig.isEven();
				},
			setInt: function(num) {
					num = Math.floor(num);
					if (num < 0)
					{
						num_sgn = true;
						num_ubig.setInt(-num);
					}
					else
					{
						num_sgn = false;
						num_ubig.setInt(num);
					}
					return this;
				},
			neg: function() {
					num_sgn = !num_sgn;
					return this;
				},
			sgn: function() { // returns -1, 0, 1
					if (this.isZero())
						return 0;
					return num_sgn ? -1 : 1;
				},
			num: function() {
					return (num_sgn ? -1 : 1) * num_ubig.num();
				},
			cmp: function(r) {
					if (num_sgn && !r.isNeg()) // -this, +r
						return -1;
					if (!num_sgn && r.isNeg()) // +this, -r
						return 1;
					return (num_sgn ? -1 : 1) * num_ubig.cmp(r._getUbig());
				},
			_addsub: function(r, flipR) {
					// case 1: -this._d + -r._d => -(this._d + r._d) (sign is maintained)
					// case 2: this._d + r._d (sign is maintained)
					// case 3: -this._d + r._d => r._d - this._d (sign toggles if r._d > this._d)
					// case 4: this._d + -r._d => this._d - r._d (sign toggles if r._d > this._d)
					if ((!flipR && num_sgn == r.isNeg()) ||
						(flipR && num_sgn != r.isNeg())) // case 1 or 2
						num_ubig.add(r._getUbig());
					else // case 3 or 4
					{
						var c = num_ubig.cmp(r._getUbig());
						if (c == 0)
							return this.setInt(0);
						if (c > 0) // num_ubig > r._getUbig(), sign stays the same
							num_ubig.sub(r._getUbig());
						else // r._d > this._d, sign is toggled
						{
							num_sgn = !num_sgn;
							num_ubig = ubig.createCopy(r._getUbig()).sub(num_ubig);
						}
					}
					return this;
				},
			add: function(r) {
					if (r.isZero())
						return this;
					if (this.isZero())
						return this.copy(r);
					return this._addsub(r, false);
				},
			sub: function(r) {
					if (r.isZero())
						return this;
					if (this.isZero())
						return this.copy(r).neg();
					return this._addsub(r, true);
				},
			mul: function(r) {
					if (this.isZero() || r.isOne())
						return this;
					if (r.isZero())
						return this.setInt(0);
					if (this.isOne())
						return this.copy(r);
					if (this.isNegOne())
						return this.copy(r).neg();
					if (r.isNeg())
						num_sgn = !num_sgn;
					if (r.isNegOne())
						return this;
					num_ubig.mul(r._getUbig());
					return this;
				},
			div: function(r) {
					if (r.isZero())
						throw 'division by zero';
					if (this.isZero() || r.isOne())
						return this;
					if (r.isNeg())
						num_sgn = !num_sgn;
					if (r.isNegOne())
						return this;
					if (num_sgn == r.isNeg())
						num_ubig.div(r._getUbig());
					else
					{
						var res = num_ubig.divmod(r._getUbig());
						if (!res.mod.isZero())
							res.div.add(ubig.createInt(1));
						num_ubig = res.div;
					}
					return this;
				},
			mod: function(r) {
					if (r.isZero())
						throw 'division by zero';
					num_ubig.mod(r._getUbig())
					if (num_ubig.isZero())
						num_sgn = false;
					if (num_sgn)
					{
						num_sgn = false;
						num_ubig = ubig.createCopy(r._getUbig()).sub(num_ubig);
					}
					return this;
				},
			pow: function(r) {
					if (r.isZero())
						return this.setInt(1);
					if (r.isNeg())
						return this.setInt(0);
					if (this.isZero() || this.isOne() || r.isOne())
						return this;
					if (num_sgn && r.isEven())
						num_sgn = false;
					num_ubig.pow(r._getUbig());
					return this;
				},
			toString: function() {
					if (this.isZero())
						return '0';
					return (num_sgn ? '-' : '') + num_ubig.toString();
				}
		};
}

function big_createFromUbig(neg, ubig_val)
{
	return big_create(neg, ubig.createCopy(ubig_val));
}

function big_createBase(str, base)
{
	var neg = false;
	if (str.substr(0, 1) == '-' || str.substr(0, 1) == '+')
	{
		neg = str.substr(0, 1) == '-';
		str = str.substr(1);
	}
	return big_create(neg, ubig.createBase(str, base));
}

function big_createCopy(num)
{
	return big_create(num.isNeg(), ubig.createCopy(num._getUbig()));
}

function big_createInt(num)
{
	return big_create(num < 0, ubig.createInt(Math.abs(num)));
}

function big_createDec(decStr)
{
	return big_createBase(decStr, 10);
}

exports.createFromUbig = big_createFromUbig;
exports.createCopy = big_createCopy;
exports.createInt = big_createInt;
exports.createBase = big_createBase;
exports.createDec = big_createDec;
